// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/media/pepper_to_video_track_adapter.h"

#include <string>

#include "base/base64.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "content/renderer/media/media_stream.h"
#include "content/renderer/media/media_stream_registry_interface.h"
#include "content/renderer/media/media_stream_video_source.h"
#include "content/renderer/media/media_stream_video_track.h"
#include "content/renderer/pepper/ppb_image_data_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "media/base/video_frame_pool.h"
#include "media/capture/video_capture_types.h"
#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/web/WebMediaStreamRegistry.h"
#include "third_party/libyuv/include/libyuv/convert.h"
#include "url/gurl.h"

namespace content {

// PpFrameWriter implements MediaStreamVideoSource and can therefore provide
// video frames to MediaStreamVideoTracks. It also implements
// FrameWriterInterface, which will be used by Pepper plugins (notably the
// Effects plugin) to inject the processed frame.
class PpFrameWriter : public MediaStreamVideoSource,
                      public FrameWriterInterface,
                      public base::SupportsWeakPtr<PpFrameWriter> {
public:
    PpFrameWriter();
    ~PpFrameWriter() override;

    // FrameWriterInterface implementation.
    // This method will be called by the Pepper host from render thread.
    void PutFrame(PPB_ImageData_Impl* image_data, int64_t time_stamp_ns) override;

protected:
    // MediaStreamVideoSource implementation.
    void GetCurrentSupportedFormats(
        int max_requested_width,
        int max_requested_height,
        double max_requested_frame_rate,
        const VideoCaptureDeviceFormatsCB& callback) override;
    void StartSourceImpl(
        const media::VideoCaptureFormat& format,
        const blink::WebMediaConstraints& constraints,
        const VideoCaptureDeliverFrameCB& frame_callback) override;
    void StopSourceImpl() override;

private:
    media::VideoFramePool frame_pool_;

    class FrameWriterDelegate;
    scoped_refptr<FrameWriterDelegate> delegate_;

    DISALLOW_COPY_AND_ASSIGN(PpFrameWriter);
};

class PpFrameWriter::FrameWriterDelegate
    : public base::RefCountedThreadSafe<FrameWriterDelegate> {
public:
    FrameWriterDelegate(
        scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
        const VideoCaptureDeliverFrameCB& new_frame_callback);

    void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame);

private:
    friend class base::RefCountedThreadSafe<FrameWriterDelegate>;
    virtual ~FrameWriterDelegate();

    void DeliverFrameOnIO(const scoped_refptr<media::VideoFrame>& frame);

    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
    VideoCaptureDeliverFrameCB new_frame_callback_;
};

PpFrameWriter::FrameWriterDelegate::FrameWriterDelegate(
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
    const VideoCaptureDeliverFrameCB& new_frame_callback)
    : io_task_runner_(io_task_runner)
    , new_frame_callback_(new_frame_callback)
{
}

PpFrameWriter::FrameWriterDelegate::~FrameWriterDelegate()
{
}

void PpFrameWriter::FrameWriterDelegate::DeliverFrame(
    const scoped_refptr<media::VideoFrame>& frame)
{
    io_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&FrameWriterDelegate::DeliverFrameOnIO, this, frame));
}

void PpFrameWriter::FrameWriterDelegate::DeliverFrameOnIO(
    const scoped_refptr<media::VideoFrame>& frame)
{
    DCHECK(io_task_runner_->BelongsToCurrentThread());
    // The local time when this frame is generated is unknown so give a null
    // value to |estimated_capture_time|.
    new_frame_callback_.Run(frame, base::TimeTicks());
}

PpFrameWriter::PpFrameWriter()
{
    DVLOG(3) << "PpFrameWriter ctor";
}

PpFrameWriter::~PpFrameWriter()
{
    DVLOG(3) << "PpFrameWriter dtor";
}

void PpFrameWriter::GetCurrentSupportedFormats(
    int max_requested_width,
    int max_requested_height,
    double max_requested_frame_rate,
    const VideoCaptureDeviceFormatsCB& callback)
{
    DCHECK(CalledOnValidThread());
    DVLOG(3) << "PpFrameWriter::GetCurrentSupportedFormats()";
    // Since the input is free to change the resolution at any point in time
    // the supported formats are unknown.
    media::VideoCaptureFormats formats;
    callback.Run(formats);
}

void PpFrameWriter::StartSourceImpl(
    const media::VideoCaptureFormat& format,
    const blink::WebMediaConstraints& constraints,
    const VideoCaptureDeliverFrameCB& frame_callback)
{
    DCHECK(CalledOnValidThread());
    DCHECK(!delegate_.get());
    DVLOG(3) << "PpFrameWriter::StartSourceImpl()";
    delegate_ = new FrameWriterDelegate(io_task_runner(), frame_callback);
    OnStartDone(MEDIA_DEVICE_OK);
}

void PpFrameWriter::StopSourceImpl()
{
    DCHECK(CalledOnValidThread());
}

// Note: PutFrame must copy or process image_data directly in this function,
// because it may be overwritten as soon as we return from this function.
void PpFrameWriter::PutFrame(PPB_ImageData_Impl* image_data,
    int64_t time_stamp_ns)
{
    DCHECK(CalledOnValidThread());
    TRACE_EVENT0("video", "PpFrameWriter::PutFrame");
    DVLOG(3) << "PpFrameWriter::PutFrame()";

    if (!image_data) {
        LOG(ERROR) << "PpFrameWriter::PutFrame - Called with NULL image_data.";
        return;
    }
    ImageDataAutoMapper mapper(image_data);
    if (!mapper.is_valid()) {
        LOG(ERROR) << "PpFrameWriter::PutFrame - "
                   << "The image could not be mapped and is unusable.";
        return;
    }
    SkBitmap bitmap(image_data->GetMappedBitmap());
    if (bitmap.empty()) {
        LOG(ERROR) << "PpFrameWriter::PutFrame - "
                   << "The image_data's mapped bitmap failed.";
        return;
    }

    SkAutoLockPixels src_lock(bitmap);
    const uint8_t* src_data = static_cast<uint8_t*>(bitmap.getPixels());
    const int src_stride = static_cast<int>(bitmap.rowBytes());
    const int width = bitmap.width();
    const int height = bitmap.height();

    // We only support PP_IMAGEDATAFORMAT_BGRA_PREMUL at the moment.
    DCHECK(image_data->format() == PP_IMAGEDATAFORMAT_BGRA_PREMUL);

    const gfx::Size frame_size(width, height);

    if (state() != MediaStreamVideoSource::STARTED)
        return;

    const base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
        time_stamp_ns / base::Time::kNanosecondsPerMicrosecond);

    scoped_refptr<media::VideoFrame> new_frame = frame_pool_.CreateFrame(media::PIXEL_FORMAT_YV12, frame_size,
        gfx::Rect(frame_size), frame_size, timestamp);

    libyuv::ARGBToI420(src_data,
        src_stride,
        new_frame->data(media::VideoFrame::kYPlane),
        new_frame->stride(media::VideoFrame::kYPlane),
        new_frame->data(media::VideoFrame::kUPlane),
        new_frame->stride(media::VideoFrame::kUPlane),
        new_frame->data(media::VideoFrame::kVPlane),
        new_frame->stride(media::VideoFrame::kVPlane),
        width,
        height);

    delegate_->DeliverFrame(new_frame);
}

// PpFrameWriterProxy is a helper class to make sure the user won't use
// PpFrameWriter after it is released (IOW its owner - WebMediaStreamSource -
// is released).
class PpFrameWriterProxy : public FrameWriterInterface {
public:
    explicit PpFrameWriterProxy(const base::WeakPtr<PpFrameWriter>& writer)
        : writer_(writer)
    {
        DCHECK(writer_);
    }

    ~PpFrameWriterProxy() override { }

    void PutFrame(PPB_ImageData_Impl* image_data,
        int64_t time_stamp_ns) override
    {
        writer_->PutFrame(image_data, time_stamp_ns);
    }

private:
    base::WeakPtr<PpFrameWriter> writer_;

    DISALLOW_COPY_AND_ASSIGN(PpFrameWriterProxy);
};

bool PepperToVideoTrackAdapter::Open(MediaStreamRegistryInterface* registry,
    const std::string& url,
    FrameWriterInterface** frame_writer)
{
    DVLOG(3) << "PepperToVideoTrackAdapter::Open";
    blink::WebMediaStream stream;
    if (registry) {
        stream = registry->GetMediaStream(url);
    } else {
        stream = blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(GURL(url));
    }
    if (stream.isNull()) {
        LOG(ERROR) << "PepperToVideoTrackAdapter::Open - invalid url: " << url;
        return false;
    }

    // Create a new native video track and add it to |stream|.
    std::string track_id;
    // According to spec, a media stream source's id should be unique per
    // application. There's no easy way to strictly achieve that. The id
    // generated with this method should be unique for most of the cases but
    // theoretically it's possible we can get an id that's duplicated with the
    // existing sources.
    base::Base64Encode(base::RandBytesAsString(64), &track_id);

    PpFrameWriter* writer = new PpFrameWriter();

    // Create a new webkit video track.
    blink::WebMediaStreamSource webkit_source;
    blink::WebMediaStreamSource::Type type = blink::WebMediaStreamSource::TypeVideo;
    blink::WebString webkit_track_id = blink::WebString::fromUTF8(track_id);
    webkit_source.initialize(webkit_track_id, type, webkit_track_id,
        false /* remote */);
    webkit_source.setExtraData(writer);

    blink::WebMediaConstraints constraints;
    constraints.initialize();
    bool track_enabled = true;

    stream.addTrack(MediaStreamVideoTrack::CreateVideoTrack(
        writer, constraints, MediaStreamVideoSource::ConstraintsCallback(),
        track_enabled));

    *frame_writer = new PpFrameWriterProxy(writer->AsWeakPtr());
    return true;
}

} // namespace content
